package cambridge.parser;

import cambridge.parser.tokens.Token;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;

public abstract class Tokenizer {
   public final static char EOL = 65535;

   private final static int BUFFER_SIZE = 50;
   private final static int maxPeek = 40;

   private final Reader reader;
   // protected InputStream input;

   private int lineNo = 1;

   private final char[] buf = new char[BUFFER_SIZE];

   private int readIndex = -1; // The last read index
   private int writeIndex = -1; // The last written index
   private int column = 0;

   private int getIndex(int no) {
      return no % BUFFER_SIZE;
   }

   private void fillBuffer() throws IOException {
      int c;
      while (true) {
         // reader.read(buf, x, y);
         c = reader.read();
         writeIndex++;
         buf[getIndex(writeIndex)] = (char) c;

         if (readIndex == -1) {
            if (c == -1 || writeIndex == BUFFER_SIZE - 1) break;
         } else {
            if (c == -1 || writeIndex - readIndex == maxPeek) break;
         }
      }
   }

   private char previousChar;

   char nextChar() throws IOException {
      if (previousChar == EOL) {
         throw new IOException("Unexpected end of file");
      }

      if (readIndex + (BUFFER_SIZE - maxPeek) == writeIndex && (int) buf[getIndex(writeIndex)] != -1) {
         fillBuffer();
      }

      char c = buf[getIndex(++readIndex)];
      if (c == '\n' && previousChar != '\r') {
         lineNo++;
         column = 0;
      } else if (c == '\r') {
         lineNo++;
         column = 0;
      } else {
         column++;
      }
      previousChar = c;
      return c;
   }

   char nextChar(int t) throws IOException {
      for (int i = 0; i < t - 1; i++) {
         nextChar();
      }
      return nextChar();
   }

   char peek(int p) {
      if (p > maxPeek) throw new IllegalArgumentException("Can not peek " + p + " characters");
      return buf[getIndex(readIndex + p)];
   }

   String peekString(int p) {
      if (p > maxPeek) throw new IllegalArgumentException("Can not peek " + p + " characters");
      char[] peek = new char[p];

      for (int i = 0; i < p; i++) {
         peek[i] = buf[getIndex(readIndex + i + 1)];
      }
      return new String(peek);
   }

   int getColumn() {
      return column;
   }

   int getLineNo() {
      return lineNo;
   }

   public Tokenizer(Reader reader) throws IOException {
      this.reader = reader;
      fillBuffer();
   }

   public Tokenizer(InputStream in) throws IOException {
      this(new BufferedReader(new InputStreamReader(in)));
   }

   public void close() throws IOException {
      reader.close();
   }

   public boolean hasMoreTokens() {
      if (readIndex == -1) return writeIndex > readIndex;
      return buf[getIndex(readIndex)] != 65535;
   }

   public abstract Token nextToken() throws IOException;
}
